package com.czh.mock.providers.text.address;

import com.alibaba.fastjson.JSON;
import com.czh.mock.exceptions.FileException;
import com.czh.mock.exceptions.InvalidArgumentException;
import com.czh.mock.exceptions.code.MockCodeEnum;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.net.URIBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

/**
 * mock地址信息
 * 行政区域信息通过高德API获取
 */
public class MockAddress {
    Logger logger = LoggerFactory.getLogger(MockAddress.class);
    /**
     * 生成随机地址的配置类
     */
    private AddressConfig config;
    private DataConfig dataConfig;
    /**
     * 单例实例
     * 该类需要保存高德的行政区域数据，避免每次实例化都去请求高德API，所以使用单例模式
     */
    private static MockAddress ins = null;

    public DataConfig getDataConfig() {
        if (this.dataConfig == null) {
            try {
                this.dataConfig = JSON.parseObject(ClassLoader.getSystemResourceAsStream("config/address/data.json"), DataConfig.class);
            } catch (IOException e) {
                logger.error("读取中文名称数据配置文件失败：", e);
                throw new FileException(MockCodeEnum.READ_DATA_CONFIG_FILE);
            }
        }
        return dataConfig;
    }

    /**
     * 省份信息
     */
    private GdDistrictEntity districts;


    private MockAddress(AddressConfig config) {
        this.config = config;
    }

    public static MockAddress build(AddressConfig config) {
        if (ins == null) {
            synchronized (MockAddress.class) {
                if (ins == null) {
                    ins = new MockAddress(config);
                }
            }
        }
        return ins;
    }

    public AddressConfig getConfig() {
        return config;
    }

    public void setConfig(AddressConfig config) {
        this.config = config;
    }

    /**
     * 生成随机的详细地址
     *
     * @return 地址
     */
    public String nextAddress() {
        Address address = this.mockWholeDistrict();
        if (this.getConfig().getLevel() == AddressConfig.LEVEL_DISTRICT) {
            return address.toString();
        }
        this.mockDetailAddress(address);
        return address.toString();
    }

    /**
     * 生成行政区域
     *
     * @return Address
     */
    private Address mockWholeDistrict() {
        // 国家级级别
        GdDistrictEntity.District country = this.getDistricts().getDistricts().get(0);
        GdDistrictEntity.District province = this.getMockedProvince(country);
        if (this.getConfig().getLevel() == AddressConfig.LEVEL_PROVINCE) {
            return new Address(province.getName(), "", "");
        }

        // 从省里面选市，二级区域
        // 台湾省目前接口返回的数据没有二级以下的区域
        String cityName = "";
        String districtName = "";
        GdDistrictEntity.District city = this.getMockedCity(province);
        if (this.getConfig().getLevel() == AddressConfig.LEVEL_CITY) {
            return new Address(province.getName(), cityName, "");
        }
        if (city == null) {
            return new Address(province.getName(), cityName, districtName);
        }
        cityName = city.getName();
        GdDistrictEntity.District district = this.getMockedDistrict(city);
        if (district != null) {
            districtName = district.getName();
        }
        return new Address(
                province.getName(),
                cityName,
                districtName
        );
    }

    private GdDistrictEntity.District getMockedDistrict(GdDistrictEntity.District city) {
        if (this.getConfig().needCacheDistrict()) {
            if (!this.getConfig().getCache().containsKey(AddressConfig.CACHE_DISTRICT)) {
                GdDistrictEntity.District district = this.mockDistrict(city);
                this.getConfig().getCache().put(AddressConfig.CACHE_DISTRICT, district);
                return district;
            }
            return this.getConfig().getCache().get(AddressConfig.CACHE_DISTRICT);
        }
        return this.mockDistrict(city);
    }

    private GdDistrictEntity.District mockDistrict(GdDistrictEntity.District city) {
        // 从市里面选区，三级区域
        // 经济特区没有第三级，所以需要判断一下
        if (!city.getDistricts().isEmpty()) {
            int districtNum = city.getDistricts().size();
            int districtIdx = RandomUtils.nextInt(0, districtNum);
            return city.getDistricts().get(districtIdx);
        }
        return null;
    }

    private GdDistrictEntity.District getMockedCity(GdDistrictEntity.District province) {
        GdDistrictEntity.District city;
        // 需要从缓存中取城市数据
        if (this.getConfig().needCacheCity()) {
            if (!this.getConfig().getCache().containsKey(AddressConfig.CACHE_CITY)) {
                // 生成城市数据
                city = this.mockCity(province);
                this.getConfig().getCache().put(AddressConfig.CACHE_CITY, city);
                return city;
            } else {
                return this.getConfig().getCache().get(AddressConfig.CACHE_CITY);
            }
        }
        // 每次都生成新的城市数据
        return this.mockCity(province);
    }

    private GdDistrictEntity.District mockCity(GdDistrictEntity.District province) {
        if (province.getDistricts().isEmpty()) {
            return null;
        }
        int cityNum = province.getDistricts().size();
        int cityIdx = RandomUtils.nextInt(0, cityNum);
        return province.getDistricts().get(cityIdx);
    }

    private GdDistrictEntity.District getMockedProvince(GdDistrictEntity.District country) {
        // 选择省，一级区域
        GdDistrictEntity.District province;
        if (this.getConfig().needCacheProvince()) {
            if (!this.getConfig().getCache().containsKey(AddressConfig.CACHE_PROVINCE)) {
                province = this.mockProvince(country);
                this.getConfig().getCache().put(AddressConfig.CACHE_PROVINCE, province);
            } else {
                province = this.getConfig().getCache().get(AddressConfig.CACHE_PROVINCE);
            }
        } else {
            province = this.mockProvince(country);
        }
        return province;
    }

    private GdDistrictEntity.District mockProvince(GdDistrictEntity.District country) {
        int provinceNum = country.getDistricts().size();
        int provinceIdx = RandomUtils.nextInt(0, provinceNum);
        return country.getDistricts().get(provinceIdx);
    }

    private void mockDetailAddress(Address address) {
        String residence = "";
        // 生成街道至小区的数据
        String street = "";
        if (this.getConfig().needCacheResidence()) {
            // 需要缓存小区数据
            if (!this.getConfig().getCacheString().containsKey(AddressConfig.CACHE_RESIDENCE)) {
                street = this.getDataConfig().getStreets().get(RandomUtils.nextInt(0, this.getDataConfig().getStreets().size()));
                residence = this.getDataConfig().getResidences().get(RandomUtils.nextInt(0, this.getDataConfig().getResidences().size()));
                this.getConfig().getCacheString().put(AddressConfig.CACHE_RESIDENCE, residence);
                this.getConfig().getCacheString().put(AddressConfig.CACHE_STREET, street);
            } else {
                residence = this.getConfig().getCacheString().get(AddressConfig.CACHE_RESIDENCE);
                street = this.getConfig().getCacheString().get(AddressConfig.CACHE_STREET);
            }
        } else {
            street = this.getDataConfig().getStreets().get(RandomUtils.nextInt(0, this.getDataConfig().getStreets().size()));
            residence = this.getDataConfig().getResidences().get(RandomUtils.nextInt(0, this.getDataConfig().getResidences().size()));
        }
        String building = this.getDataConfig().getBuildings().get(RandomUtils.nextInt(0, this.getDataConfig().getBuildings().size()));
        String roomNumber = this.getDataConfig().getRoomNumbers().get(RandomUtils.nextInt(0, this.getDataConfig().getRoomNumbers().size()));
        address.street = street;
        address.address = String.format("%s%s%s", residence, building, roomNumber);
    }

    /**
     * 请求行政区域信息
     */
    private void requestDistricts() {
        // 调整为从环境变量中读取
        String gdKey = System.getenv("GD_KEY");
        if (StringUtils.isBlank(gdKey)) {
            throw new InvalidArgumentException(MockCodeEnum.INVALID_ARGUMENT, "请在环境变量中配置GD_KEY");
        }
        String uriString = "https://restapi.amap.com/v3/config/district";
        URI uri;
        try {
            uri = new URIBuilder(uriString)
                    .addParameter("key", gdKey)
                    .addParameter("subdistrict", "3")
                    .build();
            HttpGet get = new HttpGet(uri);
            try (CloseableHttpClient client = HttpClients.createDefault()) {
                this.districts = client.execute(
                        get,
                        response -> {
                            String json = EntityUtils.toString(response.getEntity(), "UTF-8");
                            // 配置解析json时实体未定义的属性不报错
                            return JSON.parseObject(json, GdDistrictEntity.class);
                        }
                );
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    private GdDistrictEntity getDistricts() {
        if (districts == null) {
            this.requestDistricts();
        }
        return districts;
    }

    public static class Address {
        private String province = "";
        private String city = "";
        private String district = "";
        private String street = "";
        private String address = "";

        public Address() {
        }

        public Address(String province, String city, String district) {
            this.province = province;
            this.city = city;
            this.district = district;
        }

        public Address(String province, String city, String district, String street, String address) {
            this.province = province;
            this.city = city;
            this.district = district;
            this.street = street;
            this.address = address;
        }

        public String getProvince() {
            return province;
        }

        public String getCity() {
            return city;
        }

        public String getDistrict() {
            return district;
        }

        public String getStreet() {
            return street;
        }

        public String getAddress() {
            return address;
        }

        @Override
        public String toString() {
            return String.format("%s%s%s%s%s", province, city, district, street, address);
        }
    }
}
